package com.atguigu.gmall.order.service.impl;

import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.feign.cart.CartFeignClient;
import com.atguigu.gmall.feign.product.SkuInfoFeignClient;
import com.atguigu.gmall.feign.warn.WareFeignClient;
import com.atguigu.gmall.model.enums.OrderStatus;
import com.atguigu.gmall.model.enums.ProcessStatus;
import com.atguigu.gmall.model.order.OrderDetail;
import com.atguigu.gmall.model.order.OrderDetailVo;
import com.atguigu.gmall.model.order.OrderInfo;
import com.atguigu.gmall.mq.RabbitConst;
import com.atguigu.gmall.order.config.OrderConfig;
import com.atguigu.gmall.order.config.OrderProperties;
import com.atguigu.gmall.order.mapper.OrderDetailMapper;
import com.atguigu.gmall.order.mapper.OrderInfoMapper;
import com.atguigu.gmall.order.service.OrderService;
import com.atguigu.gmall.order.to.OrderSplitDetailTo;
import com.atguigu.gmall.order.to.OrderSplitTo;
import com.atguigu.gmall.order.to.WareSkuTo;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.checkerframework.checker.units.qual.A;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.sound.sampled.Line;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.*;

@Service
public class OrderServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderService {

    //ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    SkuInfoFeignClient skuInfoFeignClient;

    @Autowired
    WareFeignClient wareFeignClient;

    @Qualifier("corePool")
    @Autowired
    ThreadPoolExecutor executor;

    @Autowired
    OrderProperties orderProperties;

    @Autowired
    OrderInfoMapper orderInfoMapper;

    @Autowired
    OrderDetailMapper orderDetailMapper;

    @Autowired
    CartFeignClient cartFeignClient;

    @Autowired
    RabbitTemplate rabbitTemplate;


    /**
     * 验令牌
     *
     * @param userId
     * @param tradeNo
     * @return
     */
    @Override
    public boolean checkToken(String userId, String tradeNo) {
        ValueOperations<String, String> operations = redisTemplate.opsForValue();
        //原子验令牌
        //令牌用完就删除
        String script = "if redis.call('get', KEYS[1]) == ARGV[1]" +
                " then return redis.call('del', KEYS[1]) else return 0 end";
/*        redisTemplate.delete(RedisConst.USER_UNREPEAT_TOKEN + userId);
        //前端请求带进来，拿到令牌就删除，防止前端恶意重试
        String token = operations.get(RedisConst.USER_UNREPEAT_TOKEN + userId);*/
        Long execute = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),
                Arrays.asList(RedisConst.USER_UNREPEAT_TOKEN + userId),
                tradeNo);
        if (!StringUtils.isEmpty(execute) && execute.intValue() != 0) {
            //验令牌成功
            return true;
        }
        return false;
    }

    /**
     * 校验价格和库存
     *
     * @param orderDetailList
     * @return
     */
    @Override
    public List<String> checkSkuPriceAndStock(List<OrderDetail> orderDetailList) {

        //保存所有异步任务的集合
        List<CompletableFuture<Void>> futures = new ArrayList<>();
        List<String> errMsg = new ArrayList<>();

        if (!CollectionUtils.isEmpty(orderDetailList)) {
            //遍历每一个商品，需要购买的商品
            for (OrderDetail orderDetail : orderDetailList) {
                Long skuId = orderDetail.getSkuId();
                BigDecimal orderPrice = orderDetail.getOrderPrice();
                String skuName = orderDetail.getSkuName();
                Integer skuNum = orderDetail.getSkuNum();


                CompletableFuture<Void> futurePrice = CompletableFuture.runAsync(() -> {
                    //远程返回的最新价格
                    BigDecimal skuPrice = skuInfoFeignClient.getSkuPrice(skuId);
                    //对比
 /*               if (orderPrice.equals(skuPrice)){
                    //价格相等
                }*/
                    /**
                     * 价格验证有两种方式
                     * 1、从代码性能上考虑：远程返回的最新价格，有一个验证失败就不往下执行
                     * 2、从用户体验的角度：验证所有，即使失败了，继续往下执行 √选择
                     */
                    if (Math.abs(orderPrice.doubleValue() - skuPrice.doubleValue()) > 0.00001) {
                        //验证通过
                        errMsg.add(skuName + "：价格发生变化,请刷新确认");
                    }
                }, executor);

                CompletableFuture<Void> futureStock = CompletableFuture.runAsync(() -> {
                    //验证库存
                    String stock = wareFeignClient.hasStock(skuId, skuNum);
                    if ("0".equals(stock)) {
                        errMsg.add(skuName + "：库存不足");
                    }
                }, executor);

                futures.add(futurePrice);
                futures.add(futureStock);


            }

            //以前收集到的所有异步任务全部结束，才算结束
            CompletableFuture<?>[] futureArray = new CompletableFuture[futures.size()];
            for (int i = 0; i < futures.size(); i++) {
                futureArray[i] = futures.get(i);
            }
            CompletableFuture.allOf(futureArray).join();

        }

        return errMsg;
    }

    /**
     * 创建订单 order_info order_detail
     *
     * @param orderInfo
     * @return
     */
    @Transactional
    @Override
    public OrderInfo createOrder(OrderInfo orderInfo) {
        //1、保存订单信息 order_info
        orderInfo.sumTotalAmount(); //总价格计算
        //设置订单的状态
        orderInfo.setOrderStatus(OrderStatus.UNPAID.name());
        //用户id,controller透传了已经设置好了

        //out_trade_no:和支付宝对接的唯一流水号
        //给支付宝给一个你订单的唯一标识（对外流水号），查支付宝某一个订单是否支付成功
        //订单id(对内流水号)
        orderInfo.setOutTradeNo("ATGUIGU" + System.currentTimeMillis() + new Random().nextInt(1000));
        Date date = new Date();
        orderInfo.setCreateTime(date);
        //expire_time 过期时间,下次要支付这个订单之前，判断一下，这个订单如果是未支付而且过期了，就不给支付
        Calendar instance = Calendar.getInstance();
        Integer ttl = orderProperties.getTtl();
        instance.add(Calendar.MINUTE, ttl);
        Date time = instance.getTime();
        orderInfo.setExpireTime(time);

        //process_status ProcessStatus 进度状态
        orderInfo.setProcessStatus(ProcessStatus.UNPAID.name());

        //订单图片地址 img_url
        List<OrderDetail> detailList = orderInfo.getOrderDetailList();
        orderInfo.setImgUrl(detailList.get(0).getImgUrl());

        //保存订单
        int insert = orderInfoMapper.insert(orderInfo);

        //2、保存订单中所有的商品信息
        for (OrderDetail detail : detailList) {
            //填充数据库需要的字段
            Long orderId = orderInfo.getId();
            detail.setOrderId(orderId);
            detail.setCreateTime(new Date());
        }
        //批量保存订单详情的方法,只和数据库交互一次
        orderDetailMapper.saveBatch(detailList);

        //订单创建完成后的收尾工作
        afterOrderCreated(orderInfo);

        return orderInfo;
    }

    /**
     * 如果订单超时，我们就关单
     * 关单前判断
     *
     * @param id
     */
    //秒 分 时 日 月 周
    @Scheduled(cron = "0 0/30 * * * *")
    @Override
    public void closeOrder(Long id) {
        //去数据库修改订单状态
        //扫描所有未支付的订单
        // if ("未支付".equals(OrderStatus.UNPAID)) {//如果当前的订单的状态是        }
        OrderInfo orderInfo = orderInfoMapper.selectById(id);
        if (orderInfo.getOrderStatus().equals(OrderStatus.UNPAID.name())) {

            String name = OrderStatus.CLOSED.name();
            orderInfoMapper.updateOrderStatus(id, name);
        }

    }

    private void afterOrderCreated(OrderInfo orderInfo) {
        //1、把购物车中相关的商品删除即可
        List<Long> skuIds = new ArrayList<>();
        List<OrderDetail> detailList = orderInfo.getOrderDetailList();
        for (OrderDetail detail : detailList) {
            skuIds.add(detail.getSkuId());
        }
        //远程调用之前，feign的拦截器自己会把老请求放过去
        cartFeignClient.deleteSkuFromCart(skuIds);

        //2、提交一个定时任务  TODO 自己写定时任务 + 定时扫描补偿 ||  还能使用消息队列mq
/*        scheduledExecutorService.schedule(() -> {
            closeOrder(orderInfo.getId());
        }, 30, TimeUnit.MINUTES);*/
        //发送以一个消息
        rabbitTemplate.convertAndSend(RabbitConst.ORDER_EXCHANGE, RabbitConst.CREATE_ORDER_ROUTKEY, orderInfo.getId());

    }

    @Override
    public void changeOrderStatusPayed(String out_trade_no, String name) {
        orderInfoMapper.changeOrderStatusPayed(out_trade_no, name);
    }

    @Override
    public OrderInfo getOrderInfoWithDetailsByOutTradeNo(String outTradeNo) {
        QueryWrapper<OrderInfo> wrapper = new QueryWrapper<>();
        wrapper.eq("out_trade_no", outTradeNo);
        //查订单
        OrderInfo orderInfo = orderInfoMapper.selectOne(wrapper);
        //订单详情
        QueryWrapper<OrderDetail> wrapper1 = new QueryWrapper<>();
        wrapper1.eq("order_id", orderInfo.getId());
        List<OrderDetail> orderDetailList = orderDetailMapper.selectList(wrapper1);
        orderInfo.setOrderDetailList(orderDetailList);

        return orderInfo;
    }

    /**
     * 拆单
     *
     * @param orderId
     * @param wareSkuMap
     * @return
     */
    @Override
    public List<OrderSplitTo> splitOrder(Long orderId, String wareSkuMap) throws JsonProcessingException {
        //订单信息
        OrderInfo orderInfo = getOrderInfoWithDetailsById(orderId);

        //2、订单中商品的库存分布[{"wareId:"1",skuIds:["1","2"]},{"wareId:"2",skuIds:["3","4"]}]
        List<WareSkuTo> wareSkuTos = new ObjectMapper().readValue(wareSkuMap, new TypeReference<List<WareSkuTo>>() {
        });

        //3、拆单
        List<OrderSplitTo> orderSplitTos = new ArrayList<>();
        for (WareSkuTo wareSkuTo : wareSkuTos) {
            OrderSplitTo splitTo = new OrderSplitTo();
            //仓库id
            String wareId = wareSkuTo.getWareId();
            //上面仓库所有的商品id
            List<String> skuIds = wareSkuTo.getSkuIds();
            //TODO 把原始的订单拆分多个继续保存数据库
            OrderInfo info = new OrderInfo();
            BeanUtils.copyProperties(orderInfo, info);
            info.setId(null);
            info.setParentOrderId(orderInfo.getId());

            //1、订单商品详情
            QueryWrapper<OrderDetail> wrapper = new QueryWrapper<>();
            wrapper.in("sku_id", skuIds);
            wrapper.eq("order_id", orderInfo.getId());
            List<OrderDetail> orderDetailList = orderDetailMapper.selectList(wrapper);
            info.setOrderDetailList(orderDetailList);
            //2、重新计算拆分的价格,必须保证setOrderDetailList有东西
            info.sumTotalAmount();

            //记录拆了的这个订单是为哪个仓库的
            //info.setWareId(wareId);

            //3、保存拆分后的订单到数据库
            orderInfoMapper.insert(info);

            //4、封装给库存服务的信息
            BeanUtils.copyProperties(info,splitTo);
            splitTo.setPaymentWay("2");
            splitTo.setWareId(wareId);
            //把当前子订单id获取到
            splitTo.setOrderId(info.getId().toString());
            //5、拆完订单后，每个订单含有哪些商品
            List<OrderSplitDetailTo> detailTos = new ArrayList<>();
            for (OrderSplitDetailTo detailTo : detailTos) {
                detailTos.add(new OrderSplitDetailTo(detailTo.getSkuId().toString(),detailTo.getSkuName(),detailTo.getSkuNum()));
            }
            splitTo.setDetails(detailTos);

            //手机所有拆了的单
            orderSplitTos.add(splitTo);
        }

        //修改原始单状态变为已拆分
        orderInfoMapper.updateOrderStatus(orderInfo.getId(),OrderStatus.SPLIT.name());

        return orderSplitTos;
    }

    @Override
    public void changeOrderStatus(long parseLong, OrderStatus waitingDelever) {
        orderInfoMapper.updateOrderStatus(parseLong,waitingDelever.name());
    }

    /**
     * 按照订单id查出订单详情
     *
     * @param orderId
     * @return
     */
    private OrderInfo getOrderInfoWithDetailsById(Long orderId) {
        OrderInfo orderInfo = orderInfoMapper.selectById(orderId);
        QueryWrapper<OrderDetail> wrapper = new QueryWrapper<>();
        wrapper.eq("order_id", orderId);

        List<OrderDetail> orderDetailList = orderDetailMapper.selectList(wrapper);
        orderInfo.setOrderDetailList(orderDetailList);

        return orderInfo;
    }
}
